home *** CD-ROM | disk | FTP | other *** search
- Components
-
- If youÆve had any exposure to BorlandÆs amazing new product, Delphi, you have no doubt noticed
- the minimal amount of code required to write a fully functional application. This is thanks to
- the Visual Component Library (VCL), which contains the components necessary to create an
- application containing buttons, list boxes, and so on simply by 'drawing' them on a form at
- design time. VCL components are a significant improvement for three reasons:
-
- The component code is linked right into your application, which means that you do not have
- to include seemingly endless custom control files when you ship. However, this does not mean
- that VBX users are left in the cold -- Delphi contains full support for VBXÆs that meet VB 1.0
- specifications.
-
- Delphi has the ability to create custom components as well by simply creating a descendant
- object of a template component (TComponent) or one of its descendants. This is actually a
- powerful concept if you think about it for a moment; it means that you can derive your component
- from a fully functional one in a clean, no nonsense fashion. For example, you could derive a
- component from TStringGrid and add in-cell editing, then save it as a separate component.
-
- If all this werenÆt enough, youÆll be pleased to learn that you can debug a component, while
- an application that uses that component is running, by using DelphiÆs integrated debugger.
-
- In Delphi, there are two types of components: visual and non-visual. Visual components do
- something that you can see and often interact with. Non-visual components, on the other hand,
- encapsulate a task that has no necessary involvement with the screen at runtime, like run a
- timer, interface with a database, or in our case, implement a simple communications interface.
- You might wonder what the point of using a visual programming language with a non-visual control
- is, and why not just create a unit that contains the same code and place it in your USES
- statement. At runtime, the difference would be zero, but during your design phase, you wouldnÆt
- get the ability to visually change default values, automatically create procedures for events by
- double clicking on their names, or see at a glance exactly what is contained in your application
- -- that is the beauty of a fully visual design environment.
-
-
- Creating a Component
-
- To begin the process of creating a component, a unit must be created, a parent component must be
- decided upon, and the component must have a register procedure. Every custom component created
- in Delphi must be derived from TComponent or a descendant of it. Since we do not need graphical
- or user interface abilities, we can stick with deriving our object from TComponent. All of this
- is done like so:
-
- unit Comm;
-
- interface
-
- uses Classes;
-
- type
- TComm=class(TComponent)
- end;
-
- procedure Register;
-
- implementation
-
- procedure Register;
- begin
- RegisterComponents ('Additional',[TComm]);
- end;
-
- end.
-
- If you were to compile the above example and add it to the component library, you might be
- surprised when you found out that it is fully functional. For example, it can be added to a
- form, its position can be changed, and it has properties that can be changed and will stay
- changed when you reload the form. True, it canÆt do anything useful, but you have to start
- somewhere.
-
- Now that we have created the skeleton for the component, we can begin to flesh it out.
- One of the first things to be decided upon is properties. Properties for components are usually
- added to the published section of the class declaration. This allows the property to be
- accessed at design time. A typical property declaration looks like this:
-
- property Port:Byte
- read FPort
- write SetPort
- default 1;
-
- This indicates that Port is of type Byte. But, what is all that funny stuff after the
- declaration? That is a clever way to allow some procedure to be called when you set the value
- of a variable. The read word indicates that a variable called FPort is to be used when reading
- the value of Port. The write word indicates that the procedure SetPort is to be called when
- setting the value of Port. The write procedure for a property always has exactly one variable
- of the same type as the property itself, and usually, the variable that the corresponding
- property represents is set within it. If it had not been necessary to call a procedure when the
- value of Port was set, the declaration could have been rewritten as follows:
-
- property Port:Byte
- read FPort
- write FPort
- default tptNone;
-
- The write section now indicates that the variable FPort will be accessed for writes as
- well as reads. This brings up an important point -- properties are not variables -- they are
- access mechanisms for getting and setting values.
-
- Notice the default word in the above statements. The value after it is the same one that
- appears in the object inspector initially at design time, before you change anything. Even
- though this default value scheme exists, it is still very important to always initialize the
- variable or call the access function for the property with the same default value during your
- Create constructor. If you fail to do so, the values you set may not be the values that are
- actually used because the compiler checks to see if the value that is entered in the object
- inspector and stored with the form is the same as the default value. If it is the actual value,
- it is not set because it was assumed to have been set in the Create constructor. If this reason
- is not compelling enough, you should be aware that if you create a component at run time, the
- values indicated by the default directive are not used at all! The only initialization that
- will be done is that which occurred in the Create constructor. One final hazard concerning the
- Create constructor should be noted; always declare it with the override word, otherwise it will
- never be called at all. The reason you should be on alert for this is if you have been
- programming in Borland Pascal for any length of time, it is probably etched within your head
- that constructors can never be virtual, so you automatically hit Enter at the end of the line
- without a second thought.
-
-
- Events and Their Handlers
-
- Now that properties are taken care of, we can go on to the events. Events are properties that
- access pointers to functions. Consider the following declaration:
-
- property OnReceive:TNotifyReceiveEvent
- read FOnReceive
- write FOnReceive;
-
- This is similar to what we have seen before, except the return type is TNotifyReceiveEvent.
- One of the changes to DelphiÆs implementation of Pascal is that a function can now return nearly
- any type. The type declaration for TNotifyEvent is shown below:
-
- TNotifyReceiveEvent = procedure (Sender:TObject;Count:Word) of object;
-
- You probably recognize this as a standard function template. What you may not recognize is the
- of object part. All this does is indicate to the compiler that this function will be a member
- of an object rather than a far function.
-
- When the OnReceive property is read from or written to, what is really happening is that the
- pointer to a procedure is being accessed. When a form is created during run time, the setting
- of this pointer happens automatically. Calling the event function is now a piece of cake; all
- you have to do is call the underlying member of the property, FOnReceive, in our example.
-
-
- Implementing Communications
-
- The specification for the communications component is rather simple. We need to be able to
- assign the properties at both run-time and design-time, read and write to the comm port, and
- respond to events, such as when the receive buffer has a certain amount of data and when the
- amount of data in the transmit buffer drops below a certain point, making it ready to receive
- more. The code for the communication component is shown in Listing 1.
-
- New properties include: baud rate, number of data bits, parity, communications port,
- read buffer size, a value to indicate how full the receive buffer has to be before an event is
- triggered, number of stop bits, a value to indicate how empty the transmit buffer has to be
- before an event is triggered, and the write buffer size. All of these properties can be set at
- run time by clicking on the appropriate cell in the object inspector and selecting the
- appropriate value.
-
- Perhaps the most important property is Port, which defaults to tptNone because generally
- you do not know what port you will be using at design time. However, if youÆre doing a quick
- test program, you can set the port value using the object inspector and proceed immediately in
- your program to use the communications component without any runtime setup whatsoever. By
- setting the value of Port, what you are really doing is calling the method SetPort, which is
- ultimately responsible for opening a port and closing a previously open one if an open port
- exists.
-
- Once the port has been opened, SetPort configures it to the proper state by setting all of the
- appropriate properties such as baud rate and parity. The code that sets these properties
- is not the most efficient in the world because it calls GetCommState and SetCommState for each
- property instead of calling GetCommState, setting all of the properties and then calling
- SetCommState once. But because this is done only once during the initialization of the port, it
- was not a major concern, and I decided to keep it the way it is. However, with a few upgrades
- to the code, you can reduce the intitialization time considerably.
-
- The final step in the call to SetPort is to enable communications event posting by calling
- EnableCommNot-ification. This results in the posting of three distinct events to a window:
- CN_EVENT, CN_RECEIVE, and CN_TRANSMIT. This, of course, means that we need a window handle to
- post these events to. AllocateHWnd is simple function for creating a hidden window. It takes
- a window procedure as a parameter and returns a window handle. It is the window procedure that
- receives the communications notifications and, in turn, calls the appropriate event handler.
-
- The OnReceive and OnTransmit event handlers are straightforward. They are called when
- CN_RECEIVE and CN_TRANSMIT are posted to the window respectively. The only other processing
- that occurs before the actual event is triggered is to determine how many bytes are in the
- appropriate buffer so the count can be included as a parameter.
-
- The OnEvent event is not quite so straightforward. When the property Events is set, thereby
- calling the procedure SetEvents, a bit mask of events is created. It is this bit mask that is
- used in the call to SetCommEventMask to enable the desired events. During the receipt of a
- CN_EVENT, the events that occurred are retrieved and reset by calling GetCommEventMask. These
- events are added to a set which is then passed to the OnEvent event handler.
-
- To write data to a port, you must call the Write method:
-
- procedure TComm.Write(Data:PChar;Len:Word);
-
- Here, Data is a pointer to a block of data, and Len is the length of the block. Reading data is
- done similarly:
-
- procedure TComm.Read(Data:PChar;Len:Word);
-
- The parameters are the same as before. Remember that the value that you use for Len can be
- determined from the Count parameter provided by the OnReceive event.
-
- Error handling is minimal at best. Exceptions are not used, but rather a member function
- -- IsError -- returns True when an error state occurrs. Calling IsError also resets the error
- state. There are only three cases when IsError will return True: In trying to open a port, in
- reading from the port, and in writing to the port.
-
-
- Initializing By the Numbers
-
- When designing a component, the initialization sequence is important to keep in mind. At first
- I had the initial call to SetPort in the Create method, but a problem quickly showed up: Only
- the property values set in the Create method would be used, rather than the expected values that
- were set using the Object Inspector during design time. This is because design time properties
- are set after the Create method.
-
- The Loaded method, on the other hand, provided what I was really looking for because it is not
- called until all Object Inspector properties and events are set. This, unfortunately, created
- another minor problem: SetPort would be called twice, once during the automatic setting of all
- the properties when the form was loaded, and again during my call in Loaded. The problem is not
- strictly that the same function would be called twice without just cause, but that the first
- call to SetPort would be called at some unknown (at least to me) time. This meant that none,
- some, or all of the other properties would have been set and their write procedures called
- after SetPort was called. This can get messy if you consider that properties like
- WriteBufferSize and ReadBufferSize work by actually closing the port and reopening it. This fix
- was simple: A flag, HasBeenLoaded, is set to False during the Create method and set to True
- during the Loaded method. When SetPort is called, it checks the flag to make sure it is True
- before allowing the port to open.
-
-
- Adding the Component
-
- To add the communications component to the Component Palette, use the Install Components dialog
- box. A palette bitmap (see Figure 1) was designed for the component and will be displayed on
- the Component Palette(see Figure 2) in the ôAdditionalö section, providing the installation was
- successful.
-
-
- Our Communications Example
-
- The example program in Listing 2 and Listing 3 is a very simple terminal program that sends out
- what you type and displays what is being received. The form for the program (Listing 4 and
- Figure 3), consists of a memo box component and the communications component. To make the
- response to the received data snappy, the RxFull count property is set to one. The port number
- has been set at design time, and in my case it is one. If you try out this example, be sure to
- set this number to a port that works for you. Examination of the example programÆs code will
- reveal that it contains a mere 43 lines of Object Pascal. This is, of course, due to the
- communications component.
-
- In a nutshell, weÆve seen how a Delphi component goes together and how such a component can
- reduce the amount of code in an application, while being ready for immediate re-use by anyone
- who wishes to pull it down from the component palette. By creating components such as this one,
- we are in essence programming portions not only of our current projects, but of our future
- projects as well. Using this type of programming, you can quickly build large libraries of
- components that will allow sophisticated projects to be assembled in hours compared to the days,
- weeks, and perhaps even months they would take without them and without Delphi.
-